using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Commons.Utils
{
	static public class ReflectionHelper
	{
		private static Dictionary<string,Type> typeCache = new Dictionary<string, Type>()
		{
			{ "float", typeof(float) },
			{ "int", typeof(int) },
			{ "long", typeof(long) },
			{ "ulong", typeof(ulong) },
			{ "uint", typeof(uint) },
			{ "byte", typeof(byte) },
			{ "sbyte", typeof(sbyte) },
			{ "short", typeof(short) },
			{ "ushort", typeof(ushort) },
			{ "double", typeof(double) },
			{ "bool", typeof(bool) },
			{ "string", typeof(string) }
		};

		public static Type GetCustomType(string typeName)
		{
			Type type;
			// first lookup in cache
			if (typeCache.TryGetValue(typeName, out type))
			{
				return type;
			}

			// then find by name
			type = Type.GetType(typeName);

			// massive search
			if (type == null)
			{
				var types = from assembly in AppDomain.CurrentDomain.GetAssemblies()
							from assemblyType in assembly.GetTypes()
							where assemblyType.FullName == typeName
							select assemblyType;

				type = types.FirstOrDefault();
			}

			// cache the result
			if (type != null)
			{
				typeCache.Add(typeName, type);
			}

			return type;
		}

		public static MethodInfo FindEmptyMethod(object target, string methodName)
		{
			Type classType = target.GetType();

			return classType.GetMethod(methodName, Type.EmptyTypes);
		}

		public static object GetParameterValue(object obj, string fullname)
		{
			string[] words = fullname.Split('.');
			object current = GetProperty(obj, words[0]);
			
			for (int i = 1; i < words.Length && current != null; i++)
			{
				current = GetProperty(current, words[i]);
			}
			
			return current;
		}

		public static object GetProperty(object obj, string propertyName)
		{
			Type type = obj.GetType();

			PropertyInfo propertyInfo = type.GetProperty(propertyName);
			if (propertyInfo != null)
			{
				return propertyInfo.GetValue(obj, null);
			}

			FieldInfo fieldInfo = type.GetField(propertyName);
			if (fieldInfo != null)
			{
				return fieldInfo.GetValue(obj);
			}

			return null;
		}
		
		public static Type GetPropertyType(object obj, string propertyName)
		{
			Type type = obj.GetType();

			PropertyInfo propertyInfo = type.GetProperty(propertyName);
			if( propertyInfo != null )
			{
				return propertyInfo.GetType();
			}

			FieldInfo fieldInfo = type.GetField(propertyName);
			if (fieldInfo != null)
			{
				return fieldInfo.GetType();
			}

			return null;
		}

		public static object ConvertToType(Type type, object val)
		{
			if (type == typeof(string))
			{
				return val is string ? val : val.ToString();
			}
			else
			{
				try
				{
					return Convert.ChangeType(val, type);
				}
				catch (InvalidCastException)
				{
					if (type == typeof(int))
					{
						int result;
						int.TryParse((string)val, out result);
						return result;
					}
					else if (type == typeof(float))
					{
						float result;
						float.TryParse((string)val, out result);
						return result;
					}
					else if (type == typeof(double))
					{
						double result;
						double.TryParse((string)val, out result);
						return result;
					}
				}
			}
			return val;
		}

		public static void SetProperty(object obj, string propertyName, object val)
		{
			Type type = obj.GetType();

			PropertyInfo propertyInfo = type.GetProperty(propertyName);
			if (propertyInfo != null)
			{
				val = ConvertToType(propertyInfo.PropertyType,val);
				try
				{
					propertyInfo.SetValue(obj, val, null);
				}
				catch (Exception e)
				{
					Console.WriteLine(obj + " " + val);
					throw e;
				}
			}
			else
			{
				FieldInfo fieldInfo = type.GetField(propertyName);
				if (fieldInfo != null)
				{
					val = ConvertToType(fieldInfo.FieldType, val);
					fieldInfo.SetValue(obj, val);
				}
				else
				{
					Console.WriteLine(string.Format("No property {0} in {1}", propertyName,  obj.GetType()));
				}
			}
		}
	}
}

